/*M///////////////////////////////////////////////////////////////////////////////////////
//
//  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
//  By downloading, copying, installing or using the software you agree to this license.
//  If you do not agree to this license, do not download, install,
//  copy or use the software.
//
//
//                           License Agreement
//                For Open Source Computer Vision Library
//
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//   * Redistribution's of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//
//   * Redistribution's in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//
//   * The name of the copyright holders may not be used to endorse or promote products
//     derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/

#include "precomp.hpp"
#include "utils.hpp"
#include "grfmt_pxm.hpp"

namespace cv {

///////////////////////// P?M reader //////////////////////////////

static int ReadNumber( RLByteStream& strm, int maxdigits ) {
	int code;
	int val = 0;
	int digits = 0;

	code = strm.getByte();

	if ( !isdigit(code)) {
		do {
			if ( code == '#' ) {
				do {
					code = strm.getByte();
				} while ( code != '\n' && code != '\r' );
			}

			code = strm.getByte();

			while ( isspace(code)) {
				code = strm.getByte();
			}
		} while ( !isdigit( code ));
	}

	do {
		val = val * 10 + code - '0';
		if ( ++digits >= maxdigits ) { break; }
		code = strm.getByte();
	} while ( isdigit(code));

	return val;
}


PxMDecoder::PxMDecoder() {
	m_offset = -1;
	m_buf_supported = true;
}


PxMDecoder::~PxMDecoder() {
	close();
}

size_t PxMDecoder::signatureLength() const {
	return 3;
}

bool PxMDecoder::checkSignature( const string& signature ) const {
	return signature.size() >= 3 && signature[0] == 'P' &&
		   '1' <= signature[1] && signature[1] <= '6' &&
		   isspace(signature[2]);
}

ImageDecoder PxMDecoder::newDecoder() const {
	return new PxMDecoder;
}

void  PxMDecoder::close() {
	m_strm.close();
}


bool  PxMDecoder::readHeader() {
	bool result = false;

	if ( !m_buf.empty() ) {
		if ( !m_strm.open(m_buf) ) {
			return false;
		}
	} else if ( !m_strm.open( m_filename )) {
		return false;
	}

	try {
		int code = m_strm.getByte();
		if ( code != 'P' ) {
			throw RBS_BAD_HEADER;
		}

		code = m_strm.getByte();
		switch ( code ) {
		case '1': case '4': m_bpp = 1; break;
		case '2': case '5': m_bpp = 8; break;
		case '3': case '6': m_bpp = 24; break;
		default: throw RBS_BAD_HEADER;
		}

		m_binary = code >= '4';
		m_type = m_bpp > 8 ? CV_8UC3 : CV_8UC1;

		m_width = ReadNumber( m_strm, INT_MAX );
		m_height = ReadNumber( m_strm, INT_MAX );

		m_maxval = m_bpp == 1 ? 1 : ReadNumber( m_strm, INT_MAX );
		if ( m_maxval > 65535 ) {
			throw RBS_BAD_HEADER;
		}

		//if( m_maxval > 255 ) m_binary = false; nonsense
		if ( m_maxval > 255 ) {
			m_type = CV_MAKETYPE(CV_16U, CV_MAT_CN(m_type));
		}

		if ( m_width > 0 && m_height > 0 && m_maxval > 0 && m_maxval < (1 << 16)) {
			m_offset = m_strm.getPos();
			result = true;
		}
	} catch (...) {
	}

	if ( !result ) {
		m_offset = -1;
		m_width = m_height = -1;
		m_strm.close();
	}
	return result;
}


bool  PxMDecoder::readData( Mat& img ) {
	int color = img.channels() > 1;
	uchar* data = img.data;
	int step = img.step;
	PaletteEntry palette[256];
	bool   result = false;
	int  bit_depth = CV_ELEM_SIZE1(m_type) * 8;
	int  src_pitch = (m_width * m_bpp * bit_depth / 8 + 7) / 8;
	int  nch = CV_MAT_CN(m_type);
	int  width3 = m_width * nch;
	int  i, x, y;

	if ( m_offset < 0 || !m_strm.isOpened()) {
		return false;
	}

	AutoBuffer<uchar, 1024> _src(src_pitch + 32);
	uchar* src = _src;
	AutoBuffer<uchar, 1024> _gray_palette;
	uchar* gray_palette = _gray_palette;

	// create LUT for converting colors
	if ( bit_depth == 8 ) {
		_gray_palette.allocate(m_maxval + 1);
		gray_palette = _gray_palette;

		for ( i = 0; i <= m_maxval; i++ ) {
			gray_palette[i] = (uchar)((i * 255 / m_maxval) ^ (m_bpp == 1 ? 255 : 0));
		}

		FillGrayPalette( palette, m_bpp == 1 ? 1 : 8 , m_bpp == 1 );
	}

	try {
		m_strm.setPos( m_offset );

		switch ( m_bpp ) {
			////////////////////////// 1 BPP /////////////////////////
		case 1:
			if ( !m_binary ) {
				for ( y = 0; y < m_height; y++, data += step ) {
					for ( x = 0; x < m_width; x++ ) {
						src[x] = ReadNumber( m_strm, 1 ) != 0;
					}

					if ( color ) {
						FillColorRow8( data, src, m_width, palette );
					} else {
						FillGrayRow8( data, src, m_width, gray_palette );
					}
				}
			} else {
				for ( y = 0; y < m_height; y++, data += step ) {
					m_strm.getBytes( src, src_pitch );

					if ( color ) {
						FillColorRow1( data, src, m_width, palette );
					} else {
						FillGrayRow1( data, src, m_width, gray_palette );
					}
				}
			}
			result = true;
			break;

			////////////////////////// 8 BPP /////////////////////////
		case 8:
		case 24:
			for ( y = 0; y < m_height; y++, data += step ) {
				if ( !m_binary ) {
					for ( x = 0; x < width3; x++ ) {
						int code = ReadNumber( m_strm, INT_MAX );
						if ( (unsigned)code > (unsigned)m_maxval ) { code = m_maxval; }
						if ( bit_depth == 8 ) {
							src[x] = gray_palette[code];
						} else {
							((ushort*)src)[x] = (ushort)code;
						}
					}
				} else {
					m_strm.getBytes( src, src_pitch );
					if ( bit_depth == 16 && !isBigEndian() ) {
						for ( x = 0; x < width3; x++ ) {
							uchar v = src[x * 2];
							src[x * 2] = src[x * 2 + 1];
							src[x * 2 + 1] = v;
						}
					}
				}

				if ( img.depth() == CV_8U && bit_depth == 16 ) {
					for ( x = 0; x < width3; x++ ) {
						int v = ((ushort*)src)[x];
						src[x] = (uchar)(v >> 8);
					}
				}

				if ( m_bpp == 8 ) { // image has one channel
					if ( color ) {
						if ( img.depth() == CV_8U ) {
							uchar* d = data, *s = src, *end = src + m_width;
							for ( ; s < end; d += 3, s++) {
								d[0] = d[1] = d[2] = *s;
							}
						} else {
							ushort* d = (ushort*)data, *s = (ushort*)src, *end = ((ushort*)src) + m_width;
							for ( ; s < end; s++, d += 3) {
								d[0] = d[1] = d[2] = *s;
							}
						}
					} else {
						memcpy( data, src, m_width*(bit_depth / 8) );
					}
				} else {
					if ( color ) {
						if ( img.depth() == CV_8U ) {
							icvCvt_RGB2BGR_8u_C3R( src, 0, data, 0, cvSize(m_width, 1) );
						} else {
							icvCvt_RGB2BGR_16u_C3R( (ushort*)src, 0, (ushort*)data, 0, cvSize(m_width, 1) );
						}
					} else if ( img.depth() == CV_8U ) {
						icvCvt_BGR2Gray_8u_C3C1R( src, 0, data, 0, cvSize(m_width, 1), 2 );
					} else {
						icvCvt_BGR2Gray_16u_C3C1R( (ushort*)src, 0, (ushort*)data, 0, cvSize(m_width, 1), 2 );
					}
				}
			}
			result = true;
			break;
		default:
			assert(0);
		}
	} catch (...) {
	}

	return result;
}


//////////////////////////////////////////////////////////////////////////////////////////

PxMEncoder::PxMEncoder() {
	m_description = "Portable image format (*.pbm;*.pgm;*.ppm;*.pxm;*.pnm)";
	m_buf_supported = true;
}


PxMEncoder::~PxMEncoder() {
}


ImageEncoder  PxMEncoder::newEncoder() const {
	return new PxMEncoder;
}


bool  PxMEncoder::isFormatSupported( int depth ) const {
	return depth == CV_8U || depth == CV_16U;
}


bool  PxMEncoder::write( const Mat& img, const vector<int>& params ) {
	bool isBinary = true;

	int  width = img.cols, height = img.rows;
	int  _channels = img.channels(), depth = img.elemSize1() * 8;
	int  channels = _channels > 1 ? 3 : 1;
	int  fileStep = width * img.elemSize();
	int  x, y;

	for ( size_t i = 0; i < params.size(); i += 2 )
		if ( params[i] == CV_IMWRITE_PXM_BINARY ) {
			isBinary = params[i+1] != 0;
		}

	WLByteStream strm;

	if ( m_buf ) {
		if ( !strm.open(*m_buf) ) {
			return false;
		}
		int t = CV_MAKETYPE(img.depth(), channels);
		m_buf->reserve( alignSize(256 + (isBinary ? fileStep * height :
										 ((t == CV_8UC1 ? 4 : t == CV_8UC3 ? 4 * 3 + 2 :
										   t == CV_16UC1 ? 6 : 6 * 3 + 2)*width + 1)*height), 256));
	} else if ( !strm.open(m_filename) ) {
		return false;
	}

	int  lineLength;
	int  bufferSize = 128; // buffer that should fit a header

	if ( isBinary ) {
		lineLength = width * img.elemSize();
	} else {
		lineLength = (6 * channels + (channels > 1 ? 2 : 0)) * width + 32;
	}

	if ( bufferSize < lineLength ) {
		bufferSize = lineLength;
	}

	AutoBuffer<char> _buffer(bufferSize);
	char* buffer = _buffer;

	// write header;
	sprintf( buffer, "P%c\n%d %d\n%d\n",
			 '2' + (channels > 1 ? 1 : 0) + (isBinary ? 3 : 0),
			 width, height, (1 << depth) - 1 );

	strm.putBytes( buffer, (int)strlen(buffer) );

	for ( y = 0; y < height; y++ ) {
		uchar* data = img.data + img.step * y;
		if ( isBinary ) {
			if ( _channels == 3 ) {
				if ( depth == 8 )
					icvCvt_BGR2RGB_8u_C3R( (uchar*)data, 0,
										   (uchar*)buffer, 0, cvSize(width, 1) );
				else
					icvCvt_BGR2RGB_16u_C3R( (ushort*)data, 0,
											(ushort*)buffer, 0, cvSize(width, 1) );
			}

			// swap endianness if necessary
			if ( depth == 16 && !isBigEndian() ) {
				if ( _channels == 1 ) {
					memcpy( buffer, data, fileStep );
				}
				for ( x = 0; x < width * channels * 2; x += 2 ) {
					uchar v = buffer[x];
					buffer[x] = buffer[x + 1];
					buffer[x + 1] = v;
				}
			}
			strm.putBytes( (channels > 1 || depth > 8) ? buffer : (char*)data, fileStep );
		} else {
			char* ptr = buffer;

			if ( channels > 1 ) {
				if ( depth == 8 ) {
					for ( x = 0; x < width * channels; x += channels ) {
						sprintf( ptr, "% 4d", data[x + 2] );
						ptr += 4;
						sprintf( ptr, "% 4d", data[x + 1] );
						ptr += 4;
						sprintf( ptr, "% 4d", data[x] );
						ptr += 4;
						*ptr++ = ' ';
						*ptr++ = ' ';
					}
				} else {
					for ( x = 0; x < width * channels; x += channels ) {
						sprintf( ptr, "% 6d", ((ushort*)data)[x + 2] );
						ptr += 6;
						sprintf( ptr, "% 6d", ((ushort*)data)[x + 1] );
						ptr += 6;
						sprintf( ptr, "% 6d", ((ushort*)data)[x] );
						ptr += 6;
						*ptr++ = ' ';
						*ptr++ = ' ';
					}
				}
			} else {
				if ( depth == 8 ) {
					for ( x = 0; x < width; x++ ) {
						sprintf( ptr, "% 4d", data[x] );
						ptr += 4;
					}
				} else {
					for ( x = 0; x < width; x++ ) {
						sprintf( ptr, "% 6d", ((ushort*)data)[x] );
						ptr += 6;
					}
				}
			}

			*ptr++ = '\n';

			strm.putBytes( buffer, (int)(ptr - buffer) );
		}
	}

	strm.close();
	return true;
}

}
